package com.demarc.pesquisas.infraestrutura.web.jsf.component;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.component.html.HtmlOutputLink;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.primefaces.component.ajaxstatus.AjaxStatus;
import org.primefaces.component.confirmdialog.ConfirmDialog;
import org.primefaces.component.menubar.Menubar;
import org.primefaces.component.tree.Tree;
import org.primefaces.context.RequestContext;

import com.demarc.pesquisas.infraestrutura.excecao.InfraException;

public class JsfComponentUtils {

	public static UIComponent findComponentByClientId(String id, boolean exact) {
		id = resolveAllEL(id);

		UIComponent component = null;
		List<UIComponent> foundComponents = new ArrayList<UIComponent>();
		FacesContext context = FacesContext.getCurrentInstance();
		UIViewRoot root = context.getViewRoot();
		findComponentById(root, id, foundComponents, exact, true);

		if (!foundComponents.isEmpty()) {
			if (foundComponents.size() == 1) {
				component = foundComponents.get(0);
			} else {
				StringBuilder builder = new StringBuilder();
				builder.append(foundComponents.size());
				builder.append(" components were found with a similar client id \"");
				builder.append(id);
				builder.append("\":\n");

				for (UIComponent similar : foundComponents) {
					builder.append(similar.getClientId());
					builder.append("\n");
				}
				throw new InfraException(builder.toString());
			}
		}
		return component;
	}

	public static UIComponent findComponentByClientId(String id) {
		return findComponentByClientId(id, false);
	}

	public static UIComponent findComponentByExactClientId(String id) {
		return findComponentByClientId(id, true);
	}

	public static UIComponent findComponentById(String id, Boolean rendered) {
		return findComponentById(id, rendered, false);
	}

	// filters all components that aren't rendered
	public static UIComponent findComponentById(String id, Boolean rendered,
			Boolean exactId) {
		id = resolveAllEL(id);

		UIComponent component = null;
		List<UIComponent> foundComponents = new ArrayList<UIComponent>();
		FacesContext context = FacesContext.getCurrentInstance();
		UIViewRoot root = context.getViewRoot();
		findComponentById(root, id, foundComponents, exactId, false);

		if (rendered) {
			for (UIComponent uiComponent : foundComponents) {
				if (!uiComponent.isRendered()) {
					foundComponents.remove(uiComponent);
				}
			}
		}
		if (foundComponents.isEmpty()) {
			Logger.getLogger(JsfComponentUtils.class).warn(
					"Could not find component with id: " + id);
		} else {
			if (foundComponents.size() == 1) {
				component = foundComponents.get(0);
			} else {
				StringBuilder builder = new StringBuilder();
				builder.append(foundComponents.size());
				builder.append(" components were found with a similar id \"");
				builder.append(id);
				builder.append("\":<br />");

				for (UIComponent similar : foundComponents) {
					builder.append(similar.getClientId());
					builder.append("<br />");
				}
				throw new InfraException(builder.toString());
			}
		}
		return component;
	}

	public static String resolveAllEL(String original) {
		String text = original;
		while (text.contains("#{")) {
			int indexEL = text.indexOf("#{");
			int endEL = text.indexOf("}");

			String foundEL = text.substring(indexEL, endEL + 1);
			String resolvedEL = getCompositeAttributeValue(foundEL);

			text = text.replace(foundEL, resolvedEL);
		}
		return text;
	}

	public static UIComponent findComponentByExactId(String id) {
		return findComponentById(id, false, true);
	}

	public static UIComponent findComponentById(String id) {
		return findComponentById(id, false, false);
	}

	public static void findEditableComponents(UIComponent c,
			List<EditableValueHolder> foundComponents) {
		if (c instanceof EditableValueHolder) {
			foundComponents.add((EditableValueHolder) c);
		}
		Iterator<UIComponent> children = c.getFacetsAndChildren();
		while (children.hasNext()) {
			findEditableComponents(children.next(), foundComponents);
		}
	}

	public static String getCompositeAttributeValue(String compositeAttribute) {
		return getCompositeAttributeValue(compositeAttribute, String.class);
	}

	public static Object getCompositeAttributeObject(
			String composisteAttribute, Class<?> classType) {
		return getCompositeAttributeValue(composisteAttribute, classType);
	}

	public static <F> F getCompositeAttributeValue(String compositeAttribute,
			Class<F> attributeValueClass) {
		FacesContext context = FacesContext.getCurrentInstance();
		return context.getApplication().evaluateExpressionGet(context,
				compositeAttribute, attributeValueClass);
	}

	public static void executeJavascript(String javascriptMethod) {
		RequestContext context = RequestContext.getCurrentInstance();
		if (context != null) {
			javascriptMethod = resolveAllEL(javascriptMethod);
			context.execute(javascriptMethod);
		}
	}

	public static HttpSession getSession() {
		return (HttpSession) FacesContext.getCurrentInstance()
				.getExternalContext().getSession(true);
	}

	public static HttpServletRequest getRequest() {
		return (HttpServletRequest) FacesContext.getCurrentInstance()
				.getExternalContext().getRequest();
	}

	public static void addSessionAttribute(String attributeName,
			Object attributeValue) {
		getSession().setAttribute(attributeName, attributeValue);
	}

	public static void addRequestAttribute(String attributeName,
			Object attributeValue) {
		getRequest().setAttribute(attributeName, attributeValue);
	}

	public static Object removeSessionAttribute(String attributeName) {
		HttpSession session = getSession();
		Object attribute = session.getAttribute(attributeName);
		session.removeAttribute(attributeName);
		return attribute;
	}

	public static void addClientIdToAjaxUpdate(String clientId) {
		FacesContext.getCurrentInstance().getPartialViewContext()
				.getRenderIds().add(clientId);
	}

	public static void findComponentById(UIComponent c, String id,
			List<UIComponent> foundComponents, Boolean exactId,
			boolean useClientId) {

		Queue<UIComponent> components = new LinkedList<UIComponent>();
		components.add(c);

		while (!components.isEmpty()) {
			UIComponent component = components.poll();
			String componentId = useClientId ? component.getClientId()
					: component.getId();

			if (componentId != null) {
				if (exactId) {
					if (componentId.equals(id)) {
						foundComponents.add(component);
					}
				} else if (componentId.contains(id)) {
					foundComponents.add(component);
				}
			}
			if (verifyComponent(component)) {
				continue;
			}
			Iterator<UIComponent> children = component.getFacetsAndChildren();
			while (children.hasNext()) {
				components.add(children.next());
			}
		}
	}

	protected static boolean verifyComponent(UIComponent component) {
		return component instanceof AjaxStatus || component instanceof Menubar
				|| component instanceof ConfirmDialog
				|| component instanceof HtmlOutputLink
				|| component instanceof Tree;
	}

	public static UIComponent getCurrentComponent() {
		return UIComponent.getCurrentComponent(FacesContext
				.getCurrentInstance());
	}
}